﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System.Collections.Generic;
using Tesla.Core;
using Tesla.Math;

namespace Tesla.Content.Loaders.Model.Dxs {
    public class DxsMesh {
        private DataBuffer<int> _indices;
        private List<DxsVertex> _vertList;
        private int _matID;
        private Vector3 _translation;

        public Vector3 Translation {
            get {
                return _translation;
            }
        }

        public DataBuffer<int> Indices {
            get {
                return _indices;
            }
        }

        public List<DxsVertex> Vertices {
            get {
                return _vertList;
            }
        }

        public int MaterialID {
            get {
                return _matID;
            }
        }

        public DxsMesh(List<DxsPoly> polyList, NormalGeneration normalOptions, double creaseAngle, bool swapWinding) {
            _matID = polyList[0].MaterialID;
            _indices = new DataBuffer<int>(polyList.Count * 3);
            _vertList = new List<DxsVertex>();

            if(swapWinding) {
                foreach(DxsPoly poly in polyList) {
                    DxsVertex temp = poly.Vertices[0];
                    poly.Vertices[0] = poly.Vertices[2];
                    poly.Vertices[2] = temp;
                }
            }

            CalculateNormals(polyList, normalOptions, creaseAngle);


            //Get all the unique vertices and build the index list
            foreach(DxsPoly poly in polyList) {
                for(int i = 0; i < 3; i++) {
                    DxsVertex vertex = poly.Vertices[i];
                    int index;
                    if(IsUniqueVertex(_vertList, vertex, out index)) {
                        _vertList.Add(vertex);
                        index = _vertList.Count - 1;
                    }
                    _indices.Set(index);
                }
            }

            Vector3 center = GetCenter();
            for(int i = 0; i < _vertList.Count; i++) {
                DxsVertex v = _vertList[i];
                v.Position = v.Position - center;
                _vertList[i] = v;
            }
            _translation = center;
        }

        private Vector3 GetCenter() {
            Vector3 compVect = _vertList[0].Position;
            float minX = compVect.X;
            float minY = compVect.Y;
            float minZ = compVect.Z;

            float maxX = compVect.X;
            float maxY = compVect.Y;
            float maxZ = compVect.Z;

            for(int i = 1; i < _vertList.Count; i++) {
                compVect = _vertList[i].Position;
                if(compVect.X < minX) {
                    minX = compVect.X;
                } else if(compVect.X > maxX) {
                    maxX = compVect.X;
                }
                if(compVect.Y < minY) {
                    minY = compVect.Y;
                } else if(compVect.Y > maxY) {
                    maxY = compVect.Y;
                }
                if(compVect.Z < minZ) {
                    minZ = compVect.Z;
                } else if(compVect.Z > maxZ) {
                    maxZ = compVect.Z;
                }
            }

            Vector3 center;
            center.X = (minX + maxX) * .5f;
            center.Y = (minY + maxY) * .5f;
            center.Z = (minZ + maxZ) * .5f;
            return center;
        }

        private bool Vector2Equals(ref Vector2 a, ref Vector2 b) {
            Vector2 temp = a - b;
            if(System.Math.Abs(temp.X) < MathHelper.ZeroTolerance
                && System.Math.Abs(temp.Y) < MathHelper.ZeroTolerance) {
                    return true;
            }
            return false;
        }

        private bool Vector3Equals(ref Vector3 a, ref Vector3 b) {
            Vector3 temp = a - b;
            if(System.Math.Abs(temp.X) < MathHelper.ZeroTolerance
                && System.Math.Abs(temp.Y) < MathHelper.ZeroTolerance
                && System.Math.Abs(temp.Z) < MathHelper.ZeroTolerance) {
                return true;
            }
            return false;
        }

        private bool VertexEquals(ref DxsVertex a, ref DxsVertex b) {
            Vector2 aTex = a.TextureCoordinate;
            Vector2 bTex = b.TextureCoordinate;
            Vector2 aLight = a.LightMapCoordinate;
            Vector2 bLight = b.LightMapCoordinate;
            Vector3 aNormal = a.Normal;
            Vector3 bNormal = b.Normal;

            return Vector2Equals(ref aTex, ref bTex) && Vector2Equals(ref aLight, ref bLight) && Vector3Equals(ref aNormal, ref bNormal);
        }

        private bool IsUniqueVertex(List<DxsVertex> list, DxsVertex vertex, out int index) {
            bool isUnique = true;
            index = -1;
            for(int i = 0; i < list.Count; i++) {
                DxsVertex test = list[i];
                if(test.ID == vertex.ID) {
                    if(VertexEquals(ref vertex, ref test)) {
                        isUnique = false;
                        index = i;
                        break;
                    }
                }
            }
            return isUnique;
        }

        private float Area(Vector3 p1, Vector3 p2, Vector3 p3) {
            float a = (p1 - p2).Length();
            float b = (p1 - p2).Length();
            float c = (p2 - p3).Length();

            float sqr = (float) System.Math.Sqrt(((a + b) - c) * (a - (b + c)) * (-a + (b + c)) * (a + b + c));

            return sqr * .25f;
        }

        private void CalculateNormals(List<DxsPoly> polyList, NormalGeneration normalOptions, double creaseAngle) {
            if(normalOptions == NormalGeneration.None) {
                return;
            }

            double cosAngle = System.Math.Cos(MathHelper.DegreesToRadians * creaseAngle);
            //Loop through each polygon
            for(int i = 0; i < polyList.Count; i++) {
                DxsPoly poly = polyList[i];
                //For each vertex
                for(int j = 0; j < 3; j++) {
                    DxsVertex vert = poly.Vertices[j];
                    int vid = vert.ID;

                    Vector3 smoothNormal = poly.FaceNormal;
                    if(normalOptions != NormalGeneration.Face) {
                        //Find every poly that shares this vertex
                        for(int k = 0; k < polyList.Count; k++) {
                            if(i != k) {
                                DxsPoly otherPoly = polyList[k];
                                for(int m = 0; m < 3; m++) {
                                    DxsVertex otherVertex = otherPoly.Vertices[m];
                                    int otherVid = otherVertex.ID;
                                    //Other poly uses this vertex position, then take their normal into account
                                    if(vid == otherVid) {
                                        if(normalOptions == NormalGeneration.Crease) {
                                            if(Vector3.Dot(poly.FaceNormal, otherPoly.FaceNormal) > cosAngle) {
                                                smoothNormal += otherPoly.FaceNormal;
                                            }
                                        } else {
                                            smoothNormal += otherPoly.FaceNormal;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    smoothNormal.Normalize();
                    vert.Normal = smoothNormal;
                    poly.Vertices[j] = vert;
                }
            }
        }
    }
}
